/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxEdgeStyle =
{
  /**
	 * Class: mxEdgeStyle
	 *
	 * Provides various edge styles to be used as the values for
	 * <mxConstants.STYLE_EDGE> in a cell style.
	 *
	 * Example:
	 *
	 * (code)
	 * var style = stylesheet.getDefaultEdgeStyle();
	 * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
	 * (end)
	 *
	 * Sets the default edge style to <ElbowConnector>.
	 *
	 * Custom edge style:
	 *
	 * To write a custom edge style, a function must be added to the mxEdgeStyle
	 * object as follows:
	 *
	 * (code)
	 * mxEdgeStyle.MyStyle = function(state, source, target, points, result)
	 * {
	 *   if (source != null && target != null)
	 *   {
	 *     var pt = new mxPoint(target.getCenterX(), source.getCenterY());
	 *
	 *     if (mxUtils.contains(source, pt.x, pt.y))
	 *     {
	 *       pt.y = source.y + source.height;
	 *     }
	 *
	 *     result.push(pt);
	 *   }
	 * };
	 * (end)
	 *
	 * In the above example, a right angle is created using a point on the
	 * horizontal center of the target vertex and the vertical center of the source
	 * vertex. The code checks if that point intersects the source vertex and makes
	 * the edge straight if it does. The point is then added into the result array,
	 * which acts as the return value of the function.
	 *
	 * The new edge style should then be registered in the <mxStyleRegistry> as follows:
	 * (code)
	 * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
	 * (end)
	 *
	 * The custom edge style above can now be used in a specific edge as follows:
	 *
	 * (code)
	 * model.setStyle(edge, 'edgeStyle=myEdgeStyle');
	 * (end)
	 *
	 * Note that the key of the <mxStyleRegistry> entry for the function should
	 * be used in string values, unless <mxGraphView.allowEval> is true, in
	 * which case you can also use mxEdgeStyle.MyStyle for the value in the
	 * cell style above.
	 *
	 * Or it can be used for all edges in the graph as follows:
	 *
	 * (code)
	 * var style = graph.getStylesheet().getDefaultEdgeStyle();
	 * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
	 * (end)
	 *
	 * Note that the object can be used directly when programmatically setting
	 * the value, but the key in the <mxStyleRegistry> should be used when
	 * setting the value via a key, value pair in a cell style.
	 *
	 * Function: EntityRelation
	 *
	 * Implements an entity relation style for edges (as used in database
	 * schema diagrams). At the time the function is called, the result
	 * array contains a placeholder (null) for the first absolute point,
	 * that is, the point where the edge and source terminal are connected.
	 * The implementation of the style then adds all intermediate waypoints
	 * except for the last point, that is, the connection point between the
	 * edge and the target terminal. The first ant the last point in the
	 * result array are then replaced with mxPoints that take into account
	 * the terminal's perimeter and next point on the edge.
	 *
	 * Parameters:
	 *
	 * state - <mxCellState> that represents the edge to be updated.
	 * source - <mxCellState> that represents the source terminal.
	 * target - <mxCellState> that represents the target terminal.
	 * points - List of relative control points.
	 * result - Array of <mxPoints> that represent the actual points of the
	 * edge.
	 */
	 EntityRelation: function (state, source, target, points, result) {
    var view = state.view
	 	var graph = view.graph
	 	var segment = mxUtils.getValue(state.style,
	 			mxConstants.STYLE_SEGMENT,
	 			mxConstants.ENTITY_SEGMENT) * view.scale

    var pts = state.absolutePoints
    var p0 = pts[0]
    var pe = pts[pts.length - 1]

	 	var isSourceLeft = false

    if (p0 != null) {
      source = new mxCellState()
      source.x = p0.x
      source.y = p0.y
    } else if (source != null) {
      var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE)

      if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
				mxConstants.DIRECTION_MASK_EAST) {
        isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST
      } else {
			 	var sourceGeometry = graph.getCellGeometry(source.cell)

			 	if (sourceGeometry.relative) {
			 		isSourceLeft = sourceGeometry.x <= 0.5
			 	} else if (target != null) {
			 		isSourceLeft = target.x + target.width < source.x
			 	}
      }
    } else {
      return
    }

	 	var isTargetLeft = true

    if (pe != null) {
      target = new mxCellState()
      target.x = pe.x
      target.y = pe.y
    } else if (target != null) {
      var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE)

      if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
				mxConstants.DIRECTION_MASK_EAST) {
        isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST
      } else {
			 	var targetGeometry = graph.getCellGeometry(target.cell)

			 	if (targetGeometry.relative) {
			 		isTargetLeft = targetGeometry.x <= 0.5
			 	} else if (source != null) {
			 		isTargetLeft = source.x + source.width < target.x
			 	}
      }
	 	}

    if (source != null && target != null) {
      var x0 = (isSourceLeft) ? source.x : source.x + source.width
      var y0 = view.getRoutingCenterY(source)

      var xe = (isTargetLeft) ? target.x : target.x + target.width
      var ye = view.getRoutingCenterY(target)

      var seg = segment

      var dx = (isSourceLeft) ? -seg : seg
      var dep = new mxPoint(x0 + dx, y0)

      dx = (isTargetLeft) ? -seg : seg
      var arr = new mxPoint(xe + dx, ye)

      // Adds intermediate points if both go out on same side
      if (isSourceLeft == isTargetLeft) {
        var x = (isSourceLeft)
          ? Math.min(x0, xe) - segment
          : Math.max(x0, xe) + segment

        result.push(new mxPoint(x, y0))
        result.push(new mxPoint(x, ye))
      } else if ((dep.x < arr.x) == isSourceLeft) {
        var midY = y0 + (ye - y0) / 2

        result.push(dep)
        result.push(new mxPoint(dep.x, midY))
        result.push(new mxPoint(arr.x, midY))
        result.push(arr)
      } else {
        result.push(dep)
        result.push(arr)
      }
    }
	 },

	 /**
	 * Function: Loop
	 *
	 * Implements a self-reference, aka. loop.
	 */
  Loop: function (state, source, target, points, result) {
    var pts = state.absolutePoints

    var p0 = pts[0]
    var pe = pts[pts.length - 1]

    if (p0 != null && pe != null) {
      if (points != null && points.length > 0) {
        for (var i = 0; i < points.length; i++) {
          var pt = points[i]
          pt = state.view.transformControlPoint(state, pt)
          result.push(new mxPoint(pt.x, pt.y))
        }
      }

      return
    }

    if (source != null) {
      var view = state.view
      var graph = view.graph
      var pt = (points != null && points.length > 0) ? points[0] : null

      if (pt != null) {
        pt = view.transformControlPoint(state, pt)

        if (mxUtils.contains(source, pt.x, pt.y)) {
          pt = null
        }
      }

      var x = 0
      var dx = 0
      var y = 0
      var dy = 0

		 	var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,
		 		graph.gridSize) * view.scale
      var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
        mxConstants.DIRECTION_WEST)

      if (dir == mxConstants.DIRECTION_NORTH ||
				dir == mxConstants.DIRECTION_SOUTH) {
        x = view.getRoutingCenterX(source)
        dx = seg
      } else {
        y = view.getRoutingCenterY(source)
        dy = seg
      }

      if (pt == null ||
				pt.x < source.x ||
				pt.x > source.x + source.width) {
        if (pt != null) {
          x = pt.x
          dy = Math.max(Math.abs(y - pt.y), dy)
        } else {
          if (dir == mxConstants.DIRECTION_NORTH) {
            y = source.y - 2 * dx
          } else if (dir == mxConstants.DIRECTION_SOUTH) {
            y = source.y + source.height + 2 * dx
          } else if (dir == mxConstants.DIRECTION_EAST) {
            x = source.x - 2 * dy
          } else {
            x = source.x + source.width + 2 * dy
          }
        }
      } else if (pt != null) {
        x = view.getRoutingCenterX(source)
        dx = Math.max(Math.abs(x - pt.x), dy)
        y = pt.y
        dy = 0
      }

      result.push(new mxPoint(x - dx, y - dy))
      result.push(new mxPoint(x + dx, y + dy))
    }
  },

  /**
	 * Function: ElbowConnector
	 *
	 * Uses either <SideToSide> or <TopToBottom> depending on the horizontal
	 * flag in the cell style. <SideToSide> is used if horizontal is true or
	 * unspecified. See <EntityRelation> for a description of the
	 * parameters.
	 */
  ElbowConnector: function (state, source, target, points, result) {
    var pt = (points != null && points.length > 0) ? points[0] : null

    var vertical = false
    var horizontal = false

    if (source != null && target != null) {
      if (pt != null) {
        var left = Math.min(source.x, target.x)
        var right = Math.max(source.x + source.width,
          target.x + target.width)

        var top = Math.min(source.y, target.y)
        var bottom = Math.max(source.y + source.height,
          target.y + target.height)

        pt = state.view.transformControlPoint(state, pt)

        vertical = pt.y < top || pt.y > bottom
        horizontal = pt.x < left || pt.x > right
      } else {
        var left = Math.max(source.x, target.x)
        var right = Math.min(source.x + source.width,
          target.x + target.width)

        vertical = left == right

        if (!vertical) {
          var top = Math.max(source.y, target.y)
          var bottom = Math.min(source.y + source.height,
            target.y + target.height)

          horizontal = top == bottom
        }
      }
    }

    if (!horizontal && (vertical ||
			state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) {
      mxEdgeStyle.TopToBottom(state, source, target, points, result)
    } else {
      mxEdgeStyle.SideToSide(state, source, target, points, result)
    }
  },

  /**
	 * Function: SideToSide
	 *
	 * Implements a vertical elbow edge. See <EntityRelation> for a description
	 * of the parameters.
	 */
  SideToSide: function (state, source, target, points, result) {
    var view = state.view
    var pt = (points != null && points.length > 0) ? points[0] : null
    var pts = state.absolutePoints
    var p0 = pts[0]
    var pe = pts[pts.length - 1]

    if (pt != null) {
      pt = view.transformControlPoint(state, pt)
    }

    if (p0 != null) {
      source = new mxCellState()
      source.x = p0.x
      source.y = p0.y
    }

    if (pe != null) {
      target = new mxCellState()
      target.x = pe.x
      target.y = pe.y
    }

    if (source != null && target != null) {
      var l = Math.max(source.x, target.x)
      var r = Math.min(source.x + source.width,
							 target.x + target.width)

      var x = (pt != null) ? pt.x : Math.round(r + (l - r) / 2)

      var y1 = view.getRoutingCenterY(source)
      var y2 = view.getRoutingCenterY(target)

      if (pt != null) {
        if (pt.y >= source.y && pt.y <= source.y + source.height) {
          y1 = pt.y
        }

        if (pt.y >= target.y && pt.y <= target.y + target.height) {
          y2 = pt.y
        }
      }

      if (!mxUtils.contains(target, x, y1) &&
				!mxUtils.contains(source, x, y1)) {
        result.push(new mxPoint(x, y1))
      }

      if (!mxUtils.contains(target, x, y2) &&
				!mxUtils.contains(source, x, y2)) {
        result.push(new mxPoint(x, y2))
      }

      if (result.length == 1) {
        if (pt != null) {
          if (!mxUtils.contains(target, x, pt.y) &&
						!mxUtils.contains(source, x, pt.y)) {
            result.push(new mxPoint(x, pt.y))
          }
        } else {
          var t = Math.max(source.y, target.y)
          var b = Math.min(source.y + source.height,
							 target.y + target.height)

          result.push(new mxPoint(x, t + (b - t) / 2))
        }
      }
    }
  },

  /**
	 * Function: TopToBottom
	 *
	 * Implements a horizontal elbow edge. See <EntityRelation> for a
	 * description of the parameters.
	 */
  TopToBottom: function (state, source, target, points, result) {
    var view = state.view
    var pt = (points != null && points.length > 0) ? points[0] : null
    var pts = state.absolutePoints
    var p0 = pts[0]
    var pe = pts[pts.length - 1]

    if (pt != null) {
      pt = view.transformControlPoint(state, pt)
    }

    if (p0 != null) {
      source = new mxCellState()
      source.x = p0.x
      source.y = p0.y
    }

    if (pe != null) {
      target = new mxCellState()
      target.x = pe.x
      target.y = pe.y
    }

    if (source != null && target != null) {
      var t = Math.max(source.y, target.y)
      var b = Math.min(source.y + source.height,
							 target.y + target.height)

      var x = view.getRoutingCenterX(source)

      if (pt != null &&
				pt.x >= source.x &&
				pt.x <= source.x + source.width) {
        x = pt.x
      }

      var y = (pt != null) ? pt.y : Math.round(b + (t - b) / 2)

      if (!mxUtils.contains(target, x, y) &&
				!mxUtils.contains(source, x, y)) {
        result.push(new mxPoint(x, y))
      }

      if (pt != null &&
				pt.x >= target.x &&
				pt.x <= target.x + target.width) {
        x = pt.x
      } else {
        x = view.getRoutingCenterX(target)
      }

      if (!mxUtils.contains(target, x, y) &&
				!mxUtils.contains(source, x, y)) {
        result.push(new mxPoint(x, y))
      }

      if (result.length == 1) {
        if (pt != null && result.length == 1) {
          if (!mxUtils.contains(target, pt.x, y) &&
						!mxUtils.contains(source, pt.x, y)) {
            result.push(new mxPoint(pt.x, y))
          }
        } else {
          var l = Math.max(source.x, target.x)
          var r = Math.min(source.x + source.width,
							 target.x + target.width)

          result.push(new mxPoint(l + (r - l) / 2, y))
        }
      }
    }
  },

  /**
	 * Function: SegmentConnector
	 *
	 * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
	 * as an interactive handler for this style.
	 *
	 * state - <mxCellState> that represents the edge to be updated.
	 * sourceScaled - <mxCellState> that represents the source terminal.
	 * targetScaled - <mxCellState> that represents the target terminal.
	 * controlHints - List of relative control points.
	 * result - Array of <mxPoints> that represent the actual points of the
	 * edge.
	 *
	 */
  SegmentConnector: function (state, sourceScaled, targetScaled, controlHints, result) {
    // Creates array of all way- and terminalpoints
    var pts = mxEdgeStyle.scalePointArray(state.absolutePoints, state.view.scale)
    var source = mxEdgeStyle.scaleCellState(sourceScaled, state.view.scale)
    var target = mxEdgeStyle.scaleCellState(targetScaled, state.view.scale)
    var tol = 1

    // Whether the first segment outgoing from the source end is horizontal
    var lastPushed = (result.length > 0) ? result[0] : null
    var horizontal = true
    var hint = null

    // Adds waypoints only if outside of tolerance
    function pushPoint (pt) {
      pt.x = Math.round(pt.x * state.view.scale * 10) / 10
      pt.y = Math.round(pt.y * state.view.scale * 10) / 10

      if (lastPushed == null || Math.abs(lastPushed.x - pt.x) >= tol || Math.abs(lastPushed.y - pt.y) >= Math.max(1, state.view.scale)) {
        result.push(pt)
        lastPushed = pt
      }

      return lastPushed
    };

    // Adds the first point
    var pt = pts[0]

    if (pt == null && source != null) {
      pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source))
    } else if (pt != null) {
      pt = pt.clone()
    }

    var lastInx = pts.length - 1

    // Adds the waypoints
    if (controlHints != null && controlHints.length > 0) {
      // Converts all hints and removes nulls
      var hints = []

      for (var i = 0; i < controlHints.length; i++) {
        var tmp = state.view.transformControlPoint(state, controlHints[i], true)

        if (tmp != null) {
          hints.push(tmp)
        }
      }

      if (hints.length == 0) {
        return
      }

      // Aligns source and target hint to fixed points
      if (pt != null && hints[0] != null) {
        if (Math.abs(hints[0].x - pt.x) < tol) {
          hints[0].x = pt.x
        }

        if (Math.abs(hints[0].y - pt.y) < tol) {
          hints[0].y = pt.y
        }
      }

      var pe = pts[lastInx]

      if (pe != null && hints[hints.length - 1] != null) {
        if (Math.abs(hints[hints.length - 1].x - pe.x) < tol) {
          hints[hints.length - 1].x = pe.x
        }

        if (Math.abs(hints[hints.length - 1].y - pe.y) < tol) {
          hints[hints.length - 1].y = pe.y
        }
      }

      hint = hints[0]

      var currentTerm = source
      var currentPt = pts[0]
      var hozChan = false
      var vertChan = false
      var currentHint = hint

      if (currentPt != null) {
        currentTerm = null
      }

      // Check for alignment with fixed points and with channels
      // at source and target segments only
      for (var i = 0; i < 2; i++) {
        var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x
        var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y

        var inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&
						currentHint.y <= currentTerm.y + currentTerm.height)
        var inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&
						currentHint.x <= currentTerm.x + currentTerm.width)

        hozChan = fixedHozAlign || (currentPt == null && inHozChan)
        vertChan = fixedVertAlign || (currentPt == null && inVertChan)

        // If the current hint falls in both the hor and vert channels in the case
        // of a floating port, or if the hint is exactly co-incident with a
        // fixed point, ignore the source and try to work out the orientation
        // from the target end
        if (i == 0 && ((hozChan && vertChan) || (fixedVertAlign && fixedHozAlign))) {
        } else {
          if (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan)) {
            horizontal = !inHozChan
            break
          }

          if (vertChan || hozChan) {
            horizontal = hozChan

            if (i == 1) {
              // Work back from target end
              horizontal = hints.length % 2 == 0 ? hozChan : vertChan
            }

            break
          }
        }

        currentTerm = target
        currentPt = pts[lastInx]

        if (currentPt != null) {
          currentTerm = null
        }

        currentHint = hints[hints.length - 1]

        if (fixedVertAlign && fixedHozAlign) {
          hints = hints.slice(1)
        }
      }

      if (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||
				(pts[0] == null && source != null &&
				(hint.y < source.y || hint.y > source.y + source.height)))) {
        pushPoint(new mxPoint(pt.x, hint.y))
      } else if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||
					(pts[0] == null && source != null &&
					(hint.x < source.x || hint.x > source.x + source.width)))) {
        pushPoint(new mxPoint(hint.x, pt.y))
      }

      if (horizontal) {
        pt.y = hint.y
      } else {
        pt.x = hint.x
      }

      for (var i = 0; i < hints.length; i++) {
        horizontal = !horizontal
        hint = hints[i]

        //				mxLog.show();
        //				mxLog.debug('hint', i, hint.x, hint.y);

        if (horizontal) {
          pt.y = hint.y
        } else {
          pt.x = hint.x
        }

        pushPoint(pt.clone())
      }
    } else {
      hint = pt
      // FIXME: First click in connect preview toggles orientation
      horizontal = true
    }

    // Adds the last point
    pt = pts[lastInx]

    if (pt == null && target != null) {
      pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target))
    }

    if (pt != null) {
      if (hint != null) {
        if (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
					(pts[lastInx] == null && target != null &&
					(hint.y < target.y || hint.y > target.y + target.height)))) {
          pushPoint(new mxPoint(pt.x, hint.y))
        } else if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
						(pts[lastInx] == null && target != null &&
						(hint.x < target.x || hint.x > target.x + target.width)))) {
          pushPoint(new mxPoint(hint.x, pt.y))
        }
      }
    }

    // Removes bends inside the source terminal for floating ports
    if (pts[0] == null && source != null) {
      while (result.length > 1 && result[1] != null &&
				mxUtils.contains(source, result[1].x, result[1].y)) {
        result.splice(1, 1)
      }
    }

    // Removes bends inside the target terminal
    if (pts[lastInx] == null && target != null) {
      while (result.length > 1 && result[result.length - 1] != null &&
				mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y)) {
        result.splice(result.length - 1, 1)
      }
    }

    // Removes last point if inside tolerance with end point
    if (pe != null && result[result.length - 1] != null &&
			Math.abs(pe.x - result[result.length - 1].x) <= tol &&
			Math.abs(pe.y - result[result.length - 1].y) <= tol) {
      result.splice(result.length - 1, 1)

      // Lines up second last point in result with end point
      if (result[result.length - 1] != null) {
        if (Math.abs(result[result.length - 1].x - pe.x) < tol) {
          result[result.length - 1].x = pe.x
        }

        if (Math.abs(result[result.length - 1].y - pe.y) < tol) {
          result[result.length - 1].y = pe.y
        }
      }
    }
  },

  orthBuffer: 10,

  orthPointsFallback: true,

  dirVectors: [[-1, 0],
    [0, -1], [1, 0], [0, 1], [-1, 0], [0, -1], [1, 0]],

  wayPoints1: [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0],
	              [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],

  routePatterns: [
    [[513, 2308, 2081, 2562], [513, 1090, 514, 2184, 2114, 2561],
      [513, 1090, 514, 2564, 2184, 2562],
      [513, 2308, 2561, 1090, 514, 2568, 2308]],
    [[514, 1057, 513, 2308, 2081, 2562], [514, 2184, 2114, 2561],
      [514, 2184, 2562, 1057, 513, 2564, 2184],
      [514, 1057, 513, 2568, 2308, 2561]],
    [[1090, 514, 1057, 513, 2308, 2081, 2562], [2114, 2561],
      [1090, 2562, 1057, 513, 2564, 2184],
      [1090, 514, 1057, 513, 2308, 2561, 2568]],
    [[2081, 2562], [1057, 513, 1090, 514, 2184, 2114, 2561],
      [1057, 513, 1090, 514, 2184, 2562, 2564],
      [1057, 2561, 1090, 514, 2568, 2308]]],

  inlineRoutePatterns: [
    [null, [2114, 2568], null, null],
    [null, [514, 2081, 2114, 2568], null, null],
    [null, [2114, 2561], null, null],
    [[2081, 2562], [1057, 2114, 2568],
      [2184, 2562],
      null]],
  vertexSeperations: [],

  limits: [
	       [0, 0, 0, 0, 0, 0, 0, 0, 0],
	       [0, 0, 0, 0, 0, 0, 0, 0, 0]],

  LEFT_MASK: 32,

  TOP_MASK: 64,

  RIGHT_MASK: 128,

  BOTTOM_MASK: 256,

  LEFT: 1,

  TOP: 2,

  RIGHT: 4,

  BOTTOM: 8,

  // TODO remove magic numbers
  SIDE_MASK: 480,
  // mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
  // | mxEdgeStyle.BOTTOM_MASK,

  CENTER_MASK: 512,

  SOURCE_MASK: 1024,

  TARGET_MASK: 2048,

  VERTEX_MASK: 3072,
  // mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,

  getJettySize: function (state, isSource) {
    var value = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_SOURCE_JETTY_SIZE
      : mxConstants.STYLE_TARGET_JETTY_SIZE, mxUtils.getValue(state.style,
      mxConstants.STYLE_JETTY_SIZE, mxEdgeStyle.orthBuffer))

    if (value == 'auto') {
      // Computes the automatic jetty size
      var type = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW, mxConstants.NONE)

      if (type != mxConstants.NONE) {
        var size = mxUtils.getNumber(state.style, (isSource) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE)
        value = Math.max(2, Math.ceil((size + mxEdgeStyle.orthBuffer) / mxEdgeStyle.orthBuffer)) * mxEdgeStyle.orthBuffer
      } else {
        value = 2 * mxEdgeStyle.orthBuffer
      }
    }

    return value
  },

  /**
	 * Function: scalePointArray
	 *
	 * Scales an array of <mxPoint>
	 *
	 * Parameters:
	 *
	 * points - array of <mxPoint> to scale
	 * scale - the scaling to divide by
	 *
	 */
  scalePointArray: function (points, scale) {
    var result = []

    if (points != null) {
      for (var i = 0; i < points.length; i++) {
        if (points[i] != null) {
          var pt = new mxPoint(Math.round(points[i].x / scale * 10) / 10,
            Math.round(points[i].y / scale * 10) / 10)
          result[i] = pt
        } else {
          result[i] = null
        }
      }
    } else {
      result = null
    }

    return result
  },

  /**
	 * Function: scaleCellState
	 *
	 * Scales an <mxCellState>
	 *
	 * Parameters:
	 *
	 * state - <mxCellState> to scale
	 * scale - the scaling to divide by
	 *
	 */
  scaleCellState: function (state, scale) {
    var result = null

    if (state != null) {
      result = state.clone()
      result.setRect(Math.round(state.x / scale * 10) / 10,
        Math.round(state.y / scale * 10) / 10,
        Math.round(state.width / scale * 10) / 10,
        Math.round(state.height / scale * 10) / 10)
    } else {
      result = null
    }

    return result
  },

  /**
	 * Function: OrthConnector
	 *
	 * Implements a local orthogonal router between the given
	 * cells.
	 *
	 * Parameters:
	 *
	 * state - <mxCellState> that represents the edge to be updated.
	 * sourceScaled - <mxCellState> that represents the source terminal.
	 * targetScaled - <mxCellState> that represents the target terminal.
	 * controlHints - List of relative control points.
	 * result - Array of <mxPoints> that represent the actual points of the
	 * edge.
	 *
	 */
  OrthConnector: function (state, sourceScaled, targetScaled, controlHints, result) {
    var graph = state.view.graph
    var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell)
    var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell)

    var pts = mxEdgeStyle.scalePointArray(state.absolutePoints, state.view.scale)
    var source = mxEdgeStyle.scaleCellState(sourceScaled, state.view.scale)
    var target = mxEdgeStyle.scaleCellState(targetScaled, state.view.scale)

    var p0 = pts[0]
    var pe = pts[pts.length - 1]

    var sourceX = source != null ? source.x : p0.x
    var sourceY = source != null ? source.y : p0.y
    var sourceWidth = source != null ? source.width : 0
    var sourceHeight = source != null ? source.height : 0

    var targetX = target != null ? target.x : pe.x
    var targetY = target != null ? target.y : pe.y
    var targetWidth = target != null ? target.width : 0
    var targetHeight = target != null ? target.height : 0

    var sourceBuffer = mxEdgeStyle.getJettySize(state, true)
    var targetBuffer = mxEdgeStyle.getJettySize(state, false)

    // console.log('sourceBuffer', sourceBuffer);
    // console.log('targetBuffer', targetBuffer);
    // Workaround for loop routing within buffer zone
    if (source != null && target == source) {
      targetBuffer = Math.max(sourceBuffer, targetBuffer)
      sourceBuffer = targetBuffer
    }

    var totalBuffer = targetBuffer + sourceBuffer
    // console.log('totalBuffer', totalBuffer);
    var tooShort = false

    // Checks minimum distance for fixed points and falls back to segment connector
    if (p0 != null && pe != null) {
      var dx = pe.x - p0.x
      var dy = pe.y - p0.y

      tooShort = dx * dx + dy * dy < totalBuffer * totalBuffer
    }

    if (tooShort || (mxEdgeStyle.orthPointsFallback && (controlHints != null &&
				controlHints.length > 0)) || sourceEdge || targetEdge) {
      mxEdgeStyle.SegmentConnector(state, sourceScaled, targetScaled, controlHints, result)

      return
    }

    // Determine the side(s) of the source and target vertices
    // that the edge may connect to
    // portConstraint [source, target]
    var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL]
    var rotation = 0

    if (source != null) {
      portConstraint[0] = mxUtils.getPortConstraints(source, state, true,
        mxConstants.DIRECTION_MASK_ALL)
      rotation = mxUtils.getValue(source.style, mxConstants.STYLE_ROTATION, 0)

      // console.log('source rotation', rotation);

      if (rotation != 0) {
        var newRect = mxUtils.getBoundingBox(new mxRectangle(sourceX, sourceY, sourceWidth, sourceHeight), rotation)
        sourceX = newRect.x
        sourceY = newRect.y
        sourceWidth = newRect.width
        sourceHeight = newRect.height
      }
    }

    if (target != null) {
      portConstraint[1] = mxUtils.getPortConstraints(target, state, false,
        mxConstants.DIRECTION_MASK_ALL)
      rotation = mxUtils.getValue(target.style, mxConstants.STYLE_ROTATION, 0)

      // console.log('target rotation', rotation);

      if (rotation != 0) {
        var newRect = mxUtils.getBoundingBox(new mxRectangle(targetX, targetY, targetWidth, targetHeight), rotation)
        targetX = newRect.x
        targetY = newRect.y
        targetWidth = newRect.width
        targetHeight = newRect.height
      }
    }

    // console.log('source' , sourceX, sourceY, sourceWidth, sourceHeight);
    // console.log('targetX' , targetX, targetY, targetWidth, targetHeight);

    var dir = [0, 0]

    // Work out which faces of the vertices present against each other
    // in a way that would allow a 3-segment connection if port constraints
    // permitted.
    // geo -> [source, target] [x, y, width, height]
    var geo = [[sourceX, sourceY, sourceWidth, sourceHeight],
		            [targetX, targetY, targetWidth, targetHeight]]
    var buffer = [sourceBuffer, targetBuffer]

    for (var i = 0; i < 2; i++) {
      mxEdgeStyle.limits[i][1] = geo[i][0] - buffer[i]
      mxEdgeStyle.limits[i][2] = geo[i][1] - buffer[i]
      mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + buffer[i]
      mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + buffer[i]
    }

    // Work out which quad the target is in
    var sourceCenX = geo[0][0] + geo[0][2] / 2.0
    var sourceCenY = geo[0][1] + geo[0][3] / 2.0
    var targetCenX = geo[1][0] + geo[1][2] / 2.0
    var targetCenY = geo[1][1] + geo[1][3] / 2.0

    var dx = sourceCenX - targetCenX
    var dy = sourceCenY - targetCenY

    var quad = 0

    // 0 | 1
    // -----
    // 3 | 2

    if (dx < 0) {
      if (dy < 0) {
        quad = 2
      } else {
        quad = 1
      }
    } else {
      if (dy <= 0) {
        quad = 3

        // Special case on x = 0 and negative y
        if (dx == 0) {
          quad = 2
        }
      }
    }

    // console.log('quad', quad);

    // Check for connection constraints
    var currentTerm = null

    if (source != null) {
      currentTerm = p0
    }

    var constraint = [[0.5, 0.5], [0.5, 0.5]]

    for (var i = 0; i < 2; i++) {
      if (currentTerm != null) {
        constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2]

        if (Math.abs(currentTerm.x - geo[i][0]) <= 1) {
          dir[i] = mxConstants.DIRECTION_MASK_WEST
        } else if (Math.abs(currentTerm.x - geo[i][0] - geo[i][2]) <= 1) {
          dir[i] = mxConstants.DIRECTION_MASK_EAST
        }

        constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3]

        if (Math.abs(currentTerm.y - geo[i][1]) <= 1) {
          dir[i] = mxConstants.DIRECTION_MASK_NORTH
        } else if (Math.abs(currentTerm.y - geo[i][1] - geo[i][3]) <= 1) {
          dir[i] = mxConstants.DIRECTION_MASK_SOUTH
        }
      }

      currentTerm = null

      if (target != null) {
        currentTerm = pe
      }
    }

    var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3])
    var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2])
    var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3])
    var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2])

    mxEdgeStyle.vertexSeperations[1] = Math.max(sourceLeftDist - totalBuffer, 0)
    mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - totalBuffer, 0)
    mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - totalBuffer, 0)
    mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - totalBuffer, 0)

    //= =============================================================
    // Start of source and target direction determination

    // Work through the preferred orientations by relative positioning
    // of the vertices and list them in preferred and available order

    var dirPref = []
    var horPref = []
    var vertPref = []

    horPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST
      : mxConstants.DIRECTION_MASK_EAST
    vertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH
      : mxConstants.DIRECTION_MASK_SOUTH

    horPref[1] = mxUtils.reversePortConstraints(horPref[0])
    vertPref[1] = mxUtils.reversePortConstraints(vertPref[0])

    var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
      : sourceRightDist
    var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
      : sourceBottomDist

    var prefOrdering = [[0, 0], [0, 0]]
    var preferredOrderSet = false

    // If the preferred port isn't available, switch it
    for (var i = 0; i < 2; i++) {
      if (dir[i] != 0x0) {
        continue
      }

      if ((horPref[i] & portConstraint[i]) == 0) {
        horPref[i] = mxUtils.reversePortConstraints(horPref[i])
      }

      if ((vertPref[i] & portConstraint[i]) == 0) {
        vertPref[i] = mxUtils
          .reversePortConstraints(vertPref[i])
      }

      prefOrdering[i][0] = vertPref[i]
      prefOrdering[i][1] = horPref[i]
    }

    if (preferredVertDist > 0 &&
				preferredHorizDist > 0) {
      // Possibility of two segment edge connection
      if (((horPref[0] & portConstraint[0]) > 0) &&
					((vertPref[1] & portConstraint[1]) > 0)) {
        prefOrdering[0][0] = horPref[0]
        prefOrdering[0][1] = vertPref[0]
        prefOrdering[1][0] = vertPref[1]
        prefOrdering[1][1] = horPref[1]
        preferredOrderSet = true
      } else if (((vertPref[0] & portConstraint[0]) > 0) &&
					((horPref[1] & portConstraint[1]) > 0)) {
        prefOrdering[0][0] = vertPref[0]
        prefOrdering[0][1] = horPref[0]
        prefOrdering[1][0] = horPref[1]
        prefOrdering[1][1] = vertPref[1]
        preferredOrderSet = true
      }
    }

    if (preferredVertDist > 0 && !preferredOrderSet) {
      prefOrdering[0][0] = vertPref[0]
      prefOrdering[0][1] = horPref[0]
      prefOrdering[1][0] = vertPref[1]
      prefOrdering[1][1] = horPref[1]
      preferredOrderSet = true
    }

    if (preferredHorizDist > 0 && !preferredOrderSet) {
      prefOrdering[0][0] = horPref[0]
      prefOrdering[0][1] = vertPref[0]
      prefOrdering[1][0] = horPref[1]
      prefOrdering[1][1] = vertPref[1]
      preferredOrderSet = true
    }

    // The source and target prefs are now an ordered list of
    // the preferred port selections
    // If the list contains gaps, compact it

    for (var i = 0; i < 2; i++) {
      if (dir[i] != 0x0) {
        continue
      }

      if ((prefOrdering[i][0] & portConstraint[i]) == 0) {
        prefOrdering[i][0] = prefOrdering[i][1]
      }

      dirPref[i] = prefOrdering[i][0] & portConstraint[i]
      dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8
      dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16
      dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24

      if ((dirPref[i] & 0xF) == 0) {
        dirPref[i] = dirPref[i] << 8
      }

      if ((dirPref[i] & 0xF00) == 0) {
        dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8
      }

      if ((dirPref[i] & 0xF0000) == 0) {
        dirPref[i] = (dirPref[i] & 0xFFFF) |
						((dirPref[i] & 0xF000000) >> 8)
      }

      dir[i] = dirPref[i] & 0xF

      if (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST ||
					portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH ||
					portConstraint[i] == mxConstants.DIRECTION_MASK_EAST ||
					portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH) {
        dir[i] = portConstraint[i]
      }
    }

    //= =============================================================
    // End of source and target direction determination

    var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
      : dir[0]
    var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
      : dir[1]

    sourceIndex -= quad
    targetIndex -= quad

    if (sourceIndex < 1) {
      sourceIndex += 4
    }

    if (targetIndex < 1) {
      targetIndex += 4
    }

    var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1]

    // console.log('routePattern', routePattern);

    mxEdgeStyle.wayPoints1[0][0] = geo[0][0]
    mxEdgeStyle.wayPoints1[0][1] = geo[0][1]

    switch (dir[0]) {
      case mxConstants.DIRECTION_MASK_WEST:
        mxEdgeStyle.wayPoints1[0][0] -= sourceBuffer
        mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3]
        break
      case mxConstants.DIRECTION_MASK_SOUTH:
        mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2]
        mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + sourceBuffer
        break
      case mxConstants.DIRECTION_MASK_EAST:
        mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + sourceBuffer
        mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3]
        break
      case mxConstants.DIRECTION_MASK_NORTH:
        mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2]
        mxEdgeStyle.wayPoints1[0][1] -= sourceBuffer
        break
    }

    var currentIndex = 0

    // Orientation, 0 horizontal, 1 vertical
    var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
      : 1
    var initialOrientation = lastOrientation
    var currentOrientation = 0

    for (var i = 0; i < routePattern.length; i++) {
      var nextDirection = routePattern[i] & 0xF

      // Rotate the index of this direction by the quad
      // to get the real direction
      var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3
        : nextDirection

      directionIndex += quad

      if (directionIndex > 4) {
        directionIndex -= 4
      }

      var direction = mxEdgeStyle.dirVectors[directionIndex - 1]

      currentOrientation = (directionIndex % 2 > 0) ? 0 : 1
      // Only update the current index if the point moved
      // in the direction of the current segment move,
      // otherwise the same point is moved until there is
      // a segment direction change
      if (currentOrientation != lastOrientation) {
        currentIndex++
        // Copy the previous way point into the new one
        // We can't base the new position on index - 1
        // because sometime elbows turn out not to exist,
        // then we'd have to rewind.
        mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0]
        mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1]
      }

      var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0
      var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0
      var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5
      side = side << quad

      if (side > 0xF) {
        side = side >> 4
      }

      var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0

      if ((sou || tar) && side < 9) {
        var limit = 0
        var souTar = sou ? 0 : 1

        if (center && currentOrientation == 0) {
          limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2]
        } else if (center) {
          limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3]
        } else {
          limit = mxEdgeStyle.limits[souTar][side]
        }

        if (currentOrientation == 0) {
          var lastX = mxEdgeStyle.wayPoints1[currentIndex][0]
          var deltaX = (limit - lastX) * direction[0]

          if (deltaX > 0) {
            mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0] *
								deltaX
          }
        } else {
          var lastY = mxEdgeStyle.wayPoints1[currentIndex][1]
          var deltaY = (limit - lastY) * direction[1]

          if (deltaY > 0) {
            mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1] *
								deltaY
          }
        }
      } else if (center) {
        // Which center we're travelling to depend on the current direction
        mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0] *
						Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2)
        mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1] *
						Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2)
      }

      if (currentIndex > 0 &&
					mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation]) {
        currentIndex--
      } else {
        lastOrientation = currentOrientation
      }
    }

    for (var i = 0; i <= currentIndex; i++) {
      if (i == currentIndex) {
        // Last point can cause last segment to be in
        // same direction as jetty/approach. If so,
        // check the number of points is consistent
        // with the relative orientation of source and target
        // jx. Same orientation requires an even
        // number of turns (points), different requires
        // odd.
        var targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
          : 1
        var sameOrient = targetOrientation == initialOrientation ? 0 : 1

        // (currentIndex + 1) % 2 is 0 for even number of points,
        // 1 for odd
        if (sameOrient != (currentIndex + 1) % 2) {
          // The last point isn't required
          break
        }
      }

      result.push(new mxPoint(Math.round(mxEdgeStyle.wayPoints1[i][0] * state.view.scale * 10) / 10,
        Math.round(mxEdgeStyle.wayPoints1[i][1] * state.view.scale * 10) / 10))
    }

    // console.log(result);

    // Removes duplicates
    var index = 1

    while (index < result.length) {
      if (result[index - 1] == null || result[index] == null ||
				result[index - 1].x != result[index].x ||
				result[index - 1].y != result[index].y) {
        index++
      } else {
        result.splice(index, 1)
      }
    }
  },

  getRoutePattern: function (dir, quad, dx, dy) {
    var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
      : dir[0]
    var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
      : dir[1]

    sourceIndex -= quad
    targetIndex -= quad

    if (sourceIndex < 1) {
      sourceIndex += 4
    }
    if (targetIndex < 1) {
      targetIndex += 4
    }

    var result = routePatterns[sourceIndex - 1][targetIndex - 1]

    if (dx == 0 || dy == 0) {
      if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null) {
        result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1]
      }
    }

    return result
  }
}
